package faw.car.ccentersdk.prop;

import android.os.Parcel;
import android.os.Parcelable;

import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import ltd.faw.car.internal.HQCarLog;

/**
 * @Author zhuima
 */
public final class HQCarPropertyValue<T> implements Parcelable {

    private static final String TAG = "HQCarPropertyValue";

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private static final int TYPE_PARCELABLE_ARR = 1;
    private static final int TYPE_INT_ARR = 2;
    private static final int TYPE_FLOAT_ARR = 3;
    private static final int TYPE_DOUBLE_ARR = 4;
    private static final int TYPE_LONG_ARR = 5;
    private static final int TYPE_OTHER = 0;
    private int mPropertyId;

    /**
     * {@see VehicleArea}
     */
    private int mArea;
    private int mStatus;
    private long mTimeStamp;
    private int mSessionId;
    private int mIdentify;
    private T mValue;

    private HQCarAccessControlConfig mAccessControlConfig;
    private String mProcessName;

    // support key
    private String mPropertyKey;

    public HQCarPropertyValue(int propertyId, T value) {
        this.mPropertyId = propertyId;
        this.mIdentify = HQPIdentify.VEHICLE;
        this.mValue = value;
        this.mTimeStamp = System.currentTimeMillis();
    }

    /**
     * @param propertyId {@link HQCarPropertyIds}
     * @param identify   {@link HQPIdentify}
     * @param value
     */
    public HQCarPropertyValue(int propertyId, int identify, T value) {
        this.mPropertyId = propertyId;
        this.mIdentify = identify;
        this.mValue = value;
        this.mTimeStamp = System.currentTimeMillis();
    }

    public HQCarPropertyValue(String propertyKey, T value) {
        this.mPropertyKey = propertyKey;
        this.mIdentify = HQPIdentify.VEHICLE;
        this.mValue = value;
        this.mTimeStamp = System.currentTimeMillis();
    }

    /**
     * @param propertyKey
     * @param identify   {@link HQPIdentify}
     * @param value
     */
    public HQCarPropertyValue(String propertyKey, int identify, T value) {
        this.mPropertyKey = propertyKey;
        this.mIdentify = identify;
        this.mValue = value;
        this.mTimeStamp = System.currentTimeMillis();
    }

    public void setIdentify(int mIdentify) {
        this.mIdentify = mIdentify;
    }

    public int getIdentify() {
        return mIdentify;
    }

    public void setStatus(int mStatus) {
        this.mStatus = mStatus;
    }

    public void setPropertyId(int mPropertyId) {
        this.mPropertyId = mPropertyId;
    }

    public void setArea(int mArea) {
        this.mArea = mArea;
    }

    public void setTimeStamp(long mTimeStamp) {
        this.mTimeStamp = mTimeStamp;
    }

    public void setSessionId(int mSessionId) {
        this.mSessionId = mSessionId;
    }

    public T getValue() {
        return mValue;
    }

    public void setValue(T value) {
        this.mValue = value;
    }

    public int getArea() {
        return mArea;
    }

    public int getStatus() {
        return mStatus;
    }

    public long getTimeStamp() {
        return mTimeStamp;
    }

    public int getPropertyId() {
        return mPropertyId;
    }

    public int getSessionId() {
        return mSessionId;
    }

    public void setProcessName(String processName) {
        this.mProcessName = processName;
    }

    public String getProcessName() {
        return mProcessName;
    }

    public HQCarAccessControlConfig getAccessControlConfig() {
        return mAccessControlConfig;
    }

    public void setAccessControlConfig(HQCarAccessControlConfig accessControlConfig) {
        this.mAccessControlConfig = accessControlConfig;
    }

    public void setPropertyKey(String propertyKey) {
        this.mPropertyKey = propertyKey;
    }

    public String getPropertyKey() {
        return mPropertyKey;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public HQCarPropertyValue() {
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.mPropertyId);
        dest.writeInt(this.mArea);
        dest.writeInt(this.mStatus);
        dest.writeLong(this.mTimeStamp);
        dest.writeInt(this.mSessionId);
        dest.writeInt(this.mIdentify);

        if (mValue instanceof Object[]) {
            final Object[] arr = (Object[]) mValue;
            if (arr.length != 0) {
                final Object item = arr[0];
                if (item instanceof Parcelable) {
                    dest.writeInt(TYPE_PARCELABLE_ARR);
                    dest.writeString(item.getClass().getName());
                    dest.writeInt(arr.length);
                    for (Object parcelable : arr) {
                        dest.writeParcelable((Parcelable) parcelable, flags);
                    }
                } else {
                    if (item instanceof Integer) {
                        dest.writeInt(TYPE_INT_ARR);
                        dest.writeString(Integer.class.getName());
                        dest.writeInt(arr.length);
                        for (Object o : arr) {
                            dest.writeInt((int) o);
                        }
                    } else if (item instanceof Long) {
                        dest.writeLong(TYPE_LONG_ARR);
                        dest.writeString(Long.class.getName());
                        dest.writeInt(arr.length);
                        for (Object o : arr) {
                            dest.writeLong((long) o);
                        }
                    } else if (item instanceof Float) {
                        dest.writeFloat(TYPE_FLOAT_ARR);
                        dest.writeString(Float.class.getName());
                        dest.writeInt(arr.length);
                        for (Object o : arr) {
                            dest.writeFloat((float) o);
                        }
                    } else if (item instanceof Double) {
                        dest.writeDouble(TYPE_DOUBLE_ARR);
                        dest.writeString(Double.class.getName());
                        dest.writeInt(arr.length);
                        for (Object o : arr) {
                            dest.writeDouble((double) o);
                        }
                    }
                }
            }
        } else {
            dest.writeInt(TYPE_OTHER);
            Class<?> valueClass = mValue == null ? null : mValue.getClass();
            dest.writeString(valueClass == null ? null : valueClass.getName());
            if (String.class.equals(valueClass)) {
                final byte[] bytes = ((String) mValue).getBytes(DEFAULT_CHARSET);
                dest.writeInt(bytes.length);
                dest.writeByteArray(bytes);
            } else if (byte[].class.equals(valueClass)) {
                dest.writeInt(((byte[]) mValue).length);
                dest.writeByteArray((byte[]) mValue);
            } else {
                dest.writeValue(mValue);
            }
        }
        dest.writeParcelable(mAccessControlConfig, 0);
        dest.writeString(mProcessName);
        dest.writeString(mPropertyKey);
    }

    public void readFromParcel(Parcel source) {
        this.mPropertyId = source.readInt();
        this.mArea = source.readInt();
        this.mStatus = source.readInt();
        this.mTimeStamp = source.readLong();
        this.mSessionId = source.readInt();
        this.mIdentify = source.readInt();

        int parcelableArrayFlag = source.readInt();
        String valueClassName = source.readString();
        if (valueClassName == null) {
            mValue = null;
            source.readInt();// null value.
        } else {
            Class<?> valueClass;
            try {
                valueClass = Class.forName(valueClassName);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Class not found: " + valueClassName);
            }
            if (parcelableArrayFlag == TYPE_PARCELABLE_ARR) {
                int size = source.readInt();
                Object[] valueArr = (Object[]) Array.newInstance(valueClass, size);
                for (int i = 0; i < size; i++) {
                    valueArr[i] = source.readParcelable(valueClass.getClassLoader());
                }
                mValue = (T) valueArr;
            } else if (parcelableArrayFlag == TYPE_INT_ARR) {
                int size = source.readInt();
                int dataArr[] = new int[size];
                for (int i = 0; i < size; i++) {
                    dataArr[i] = source.readInt();
                }
                mValue = (T) dataArr;
            } else if (parcelableArrayFlag == TYPE_FLOAT_ARR) {
                int size = source.readInt();
                float dataArr[] = new float[size];
                for (int i = 0; i < size; i++) {
                    dataArr[i] = source.readFloat();
                }
                mValue = (T) dataArr;
            } else if (parcelableArrayFlag == TYPE_DOUBLE_ARR) {
                int size = source.readInt();
                double dataArr[] = new double[size];
                for (int i = 0; i < size; i++) {
                    dataArr[i] = source.readDouble();
                }
                mValue = (T) dataArr;
            } else if (parcelableArrayFlag == TYPE_LONG_ARR) {
                int size = source.readInt();
                long dataArr[] = new long[size];
                for (int i = 0; i < size; i++) {
                    dataArr[i] = source.readLong();
                }
                mValue = (T) dataArr;
            } else {
                if (String.class.equals(valueClass)) {
                    int len = source.readInt();
                    byte[] bytes = new byte[len];
                    source.readByteArray(bytes);
                    mValue = (T) new String(bytes, DEFAULT_CHARSET);
                } else if (byte[].class.equals(valueClass)) {
                    int len = source.readInt();
                    byte[] bytes = new byte[len];
                    source.readByteArray(bytes);
                    mValue = (T) bytes;
                } else {
                    mValue = (T) source.readValue(valueClass.getClassLoader());
                }
            }
        }
        try {
            this.mAccessControlConfig = source.readParcelable(HQCarAccessControlConfig.class.getClassLoader());
        } catch (Exception e) {
            HQCarLog.e(TAG, "parse mAccessControlConfig failed.", e);
        }
        mProcessName = source.readString();
        try {
            mPropertyKey = source.readString();
        } catch (Exception e) {
            HQCarLog.e(TAG, "parse mPropertyKey failed.", e);
        }
    }

    protected HQCarPropertyValue(Parcel in) {
        readFromParcel(in);
    }

    public static final Creator<HQCarPropertyValue> CREATOR = new Creator<HQCarPropertyValue>() {
        @Override
        public HQCarPropertyValue createFromParcel(Parcel source) {
            return new HQCarPropertyValue(source);
        }

        @Override
        public HQCarPropertyValue[] newArray(int size) {
            return new HQCarPropertyValue[size];
        }
    };

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public String toString() {
        return "HQCarPropertyValue{" +
                "mPropertyId=" + mPropertyId +
                ", mArea=" + mArea +
                ", mStatus=" + mStatus +
                ", mTimeStamp=" + mTimeStamp +
                ", mSessionId=" + mSessionId +
                ", mIdentify=" + mIdentify +
                ", mValue=" + mValue +
                ", mAccessControlConfig=" + mAccessControlConfig +
                ", mProcessName='" + mProcessName + '\'' +
                ", mPropertyKey='" + mPropertyKey + '\'' +
                '}';
    }
}
